﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Input;

namespace UMLSynthesizer.Tasks.ViewModel
{
    internal class SourceCodeSelectorViewModel : WorkspaceViewModel
    {
        #region Declarations

        enum SourceOrigins { File, Directory };

        #endregion

        #region Fields and RelayCommands

        SourceOrigins _sourceOrigin;
        Language _selectedLanguage;

        string _selectedSourceFile;

        string _selectedSourceDirectory;
        IEnumerable<string> _selectedFileExtensions;
        bool _processSubdirectoriesOption;

        RelayCommand _selectSourceFileCommand;
        RelayCommand _selectSourceDirectoryCommand;
        RelayCommand _startScanAndCloseCommand;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="availableLanguages">Available languages</param>
        /// <param name="parameters">
        /// Parameters:
        ///     -file: select file as source origin
        ///     -directory: select directory as source origin
        ///     -lang "LangName": select "LangName" as language
        ///     -path "Path": select "Path" as path
        ///     -extensions "ExtensionList": select "ExtensionList" as extension list
        ///     -subdir: process sub directories
        /// </param>
        public SourceCodeSelectorViewModel(IEnumerable<Language> availableLanguages, string[] parameters = null)
        {
            //Check parameters
            if (availableLanguages == null || availableLanguages.Count() == 0)
                throw new ArgumentException("There has to be at least an accepted language");

            //Read parameters
            SourceOrigins? forcedSourceOrigin = null;
            string forcedLang = null;
            string forcedPath = null;
            bool isReadingExtensions = false;
            List<string> forcedExtensions = null;
            bool forcedSubdir = false;
            if(parameters != null)
                for (int i = 0; i < parameters.Length; i++)
                    switch (parameters[i].ToLower())
                    {
                        case "-file":
                            forcedSourceOrigin = SourceOrigins.File;
                            isReadingExtensions = false;
                            break;

                        case "-directory":
                            forcedSourceOrigin = SourceOrigins.Directory;
                            isReadingExtensions = false;
                            break;

                        case "-lang":
                            if (++i < parameters.Length)
                                forcedLang = parameters[i];
                            else
                                throw new ArgumentException("Expected language name");
                            isReadingExtensions = false;
                            break;

                        case "-path":
                            if (++i < parameters.Length)
                                forcedPath = parameters[i];
                            else
                                throw new ArgumentException("Expected path");
                            isReadingExtensions = false;
                            break;

                        case "-extensions":
                            isReadingExtensions = true;
                            forcedExtensions = new List<string>();
                            break;

                        case "-subdir":
                            forcedSubdir = true;
                            isReadingExtensions = false;
                            break;

                        default:
                            if (!isReadingExtensions)
                                throw new ArgumentException("Invalid parameter: " + parameters[i]);
                            else
                                forcedExtensions.Add(parameters[i]);
                            break;
                    }

            //Initialize
            if (forcedSourceOrigin.HasValue)
                _sourceOrigin = forcedSourceOrigin.Value;
            else
                _sourceOrigin = SourceOrigins.File;
            AvailableLanguages = new List<Language>(availableLanguages).AsReadOnly();
            if (forcedLang == null)
                SelectedLanguage = AvailableLanguages.ElementAtOrDefault(0);
            else
                SelectedLanguage = AvailableLanguages.FirstOrDefault(lang => lang.Name == forcedLang);
            if(forcedPath != null)
                switch(_sourceOrigin)
                {
                    case SourceOrigins.Directory:
                        SelectedSourceDirectory = forcedPath;
                        break;
                    case SourceOrigins.File:
                        SelectedSourceFile = forcedPath;
                        break;
                }
            if (forcedExtensions != null)
                SelectedFileExtensions = forcedExtensions;
            if (forcedSubdir)
                ProcessSubdirectoriesOption = true;
            ExitStateOK = false;

            SourceFiles = new string[0];
        }

        #endregion

        #region Properties

        /// <summary>
        /// True if the source origin is a single file, false if is a directory
        /// </summary>
        public bool IsSourceOriginAFile
        {
            get { return _sourceOrigin == SourceOrigins.File; }
            set
            {
                if((value && _sourceOrigin == SourceOrigins.File) ||
                    (!value && _sourceOrigin != SourceOrigins.File))
                    return;
                _sourceOrigin = (value ? SourceOrigins.File : SourceOrigins.Directory);
                OnPropertyChanged("IsSourceOriginAFile");
            }
        }
        /// <summary>
        /// True if the source origin is a directory, false if is a file
        /// </summary>
        public bool IsSourceOriginADirectory
        {
            get { return _sourceOrigin == SourceOrigins.Directory; }
            set
            {
                if((value && _sourceOrigin == SourceOrigins.Directory) ||
                    (!value && _sourceOrigin != SourceOrigins.Directory))
                    return;
                _sourceOrigin = (value ? SourceOrigins.Directory : SourceOrigins.File);
                OnPropertyChanged("IsSourceOriginADirectory");
            }
        }
        /// <summary>
        /// The list of available languages
        /// </summary>
        public ReadOnlyCollection<Language> AvailableLanguages { get; private set; }
        /// <summary>
        /// The selected language
        /// </summary>
        public Language SelectedLanguage
        {
            get { return _selectedLanguage; }
            set
            {
                if (value == _selectedLanguage)
                    return;
                _selectedLanguage = value;
                SelectedFileExtensions = value.FileExtensions;
                OnPropertyChanged("SelectedLanguage");
            }
        }

        /// <summary>
        /// Selected source file
        /// </summary>
        public string SelectedSourceFile
        {
            get { return _selectedSourceFile; }
            set
            {
                if (value == _selectedSourceFile)
                    return;
                _selectedSourceFile = value;
                OnPropertyChanged("SelectedSourceFile");
            }
        }

        /// <summary>
        /// Selected directory where search sources
        /// </summary>
        public string SelectedSourceDirectory
        {
            get { return _selectedSourceDirectory; }
            set
            {
                if (value == _selectedSourceDirectory)
                    return;
                _selectedSourceDirectory = value;
                OnPropertyChanged("SelectedSourceDirectory");
            }
        }
        /// <summary>
        /// Extensions of source files to search
        /// </summary>
        public IEnumerable<string> SelectedFileExtensions
        {
            get { return _selectedFileExtensions; }
            set
            {
                if (value == _selectedFileExtensions)
                    return;
                _selectedFileExtensions = value;
                OnPropertyChanged("SelectedFileExtensions");
            }
        }
        /// <summary>
        /// True if a directory scan has to examinate also subdirectories, false otherwise
        /// </summary>
        public bool ProcessSubdirectoriesOption
        {
            get { return _processSubdirectoriesOption; }
            set
            {
                if (value == _processSubdirectoriesOption)
                    return;
                _processSubdirectoriesOption = value;
                OnPropertyChanged("ProcessSubdirectoriesOption");
            }
        }

        /// <summary>
        /// True if closing the window the user has clicked on OK button. False otherwise
        /// </summary>
        public bool ExitStateOK { get; private set; }

        /// <summary>
        /// The set of selected source files
        /// </summary>
        public IEnumerable<string> SourceFiles { get; private set; }

        #endregion

        #region Commands

        /// <summary>
        /// Command for select a file
        /// </summary>
        public ICommand SelectSourceFileCommand
        {
            get
            {
                if (_selectSourceFileCommand == null)
                {
                    _selectSourceFileCommand = new RelayCommand(
                        param =>
                        {
                            OpenFileDialog fileDiag = new OpenFileDialog();
                            fileDiag.CheckFileExists = true;
                            fileDiag.Multiselect = false;
                            fileDiag.Filter = CreateFileFilter(SelectedLanguage.Name + " Source Files",
                                SelectedLanguage.FileExtensions) + "|" +
                                CreateFileFilter("All Files", new string[0]);
                            if (fileDiag.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                                SelectedSourceFile = fileDiag.FileName;
                        });
                }
                return _selectSourceFileCommand;
            }
        }
        /// <summary>
        /// Command for select a directory
        /// </summary>
        public ICommand SelectSourceDirectoryCommand
        {
            get
            {
                if (_selectSourceDirectoryCommand == null)
                {
                    _selectSourceDirectoryCommand = new RelayCommand(
                        param =>
                        {
                            FolderBrowserDialog foldDiag = new FolderBrowserDialog();
                            if (foldDiag.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                                SelectedSourceDirectory = foldDiag.SelectedPath;
                        });
                }
                return _selectSourceDirectoryCommand;
            }
        }
        /// <summary>
        /// Close the window and make a source file selection
        /// </summary>
        public ICommand StartScanAndCloseCommand
        {
            get
            {
                if(_startScanAndCloseCommand == null)
                {
                    _startScanAndCloseCommand = new RelayCommand(
                        param =>
                        {
                            if (IsSourceOriginAFile)
                                SourceFiles = new string[] { SelectedSourceFile };
                            else
                                SourceFiles = ScanSourceFiles(SelectedSourceDirectory, ProcessSubdirectoriesOption,
                                    SelectedFileExtensions);

                            ExitStateOK = true;
                            if (CloseCommand.CanExecute(param))
                                CloseCommand.Execute(param);
                        },
                        param =>
                        {
                            //check language selection
                            if (SelectedLanguage == null)
                                return false;

                            //check path
                            if((IsSourceOriginAFile && !IsValidPath(SourceOrigins.File, SelectedSourceFile)) ||
                                IsSourceOriginADirectory && !IsValidPath(SourceOrigins.Directory, SelectedSourceDirectory))
                                return false;

                            //check extensions if source origin is a directory
                            if (IsSourceOriginADirectory)
                            {
                                //must exists at least an extension
                                if (SelectedFileExtensions.Count() == 0)
                                    return false;
                                //extensions have to be valid
                                foreach (var extension in SelectedFileExtensions)
                                    if (!IsValidExtension(extension))
                                        return false;
                            }

                            return true;
                        });
                }
                return _startScanAndCloseCommand;
            }
        }

        #endregion

        #region Private helpers

        /// <summary>
        /// Create a filter string for file selections into file open dialog
        /// </summary>
        /// <param name="filterLabel">The name of the filter</param>
        /// <param name="extensions">Extensions to filter. If empty accept any extension</param>
        /// <returns>The string of the filter</returns>
        static string CreateFileFilter(string filterLabel, IEnumerable<string> extensions)
        {
            //create name
            string fileFilter = filterLabel + " (";
            bool useComma = false;
            if (extensions.Count() != 0)
                foreach (var extension in extensions)
                {
                    if (useComma)
                        fileFilter += ", ";
                    useComma = true;
                    fileFilter += "*" + extension;
                }
            else
                fileFilter += "*.*";
            fileFilter += ")|";
            //create filter
            useComma = false;
            if (extensions.Count() != 0)
                foreach (var extension in extensions)
                {
                    if (useComma)
                        fileFilter += ";";
                    useComma = true;
                    fileFilter += "*" + extension;
                }
            else
                fileFilter += "*.*";
            return fileFilter;
        }
        /// <summary>
        /// Check if a path is a valid path and if exists
        /// </summary>
        /// <param name="pathType">The type of path</param>
        /// <param name="path">The path to check</param>
        /// <returns>True if is a valid path, false otherwise</returns>
        static bool IsValidPath(SourceOrigins pathType, string path)
        {
            try
            {
                switch (pathType)
                {
                    case SourceOrigins.File:
                        return File.Exists(path);
                    case SourceOrigins.Directory:
                        return Directory.Exists(path);
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }
        static bool IsValidExtension(string extension)
        {
            //check not empty or not only dot
            if (extension == null || extension.Trim().Length <= 1)
                return false;
            //check invalid characters
            foreach (var character in Path.GetInvalidFileNameChars())
                if (extension.Contains(character))
                    return false;
            //starts with dot
            if (extension[0] != '.')
                return false;
            //can't finish with dot
            if (extension[extension.Length - 1] == '.')
                return false;

            return true;
        }
        /// <summary>
        /// Perform a scan of the file system in order to find source files
        /// </summary>
        /// <param name="rootDirectory">The root directory to scan</param>
        /// <param name="visitSubdirectories">True if has to visit subdirectories</param>
        /// <param name="extensionsFilter">Extensions to filter</param>
        static IEnumerable<string> ScanSourceFiles(string rootDirectory, bool visitSubdirectories,
            IEnumerable<string> extensionsFilter)
        {
            List<string> selectedFiles = new List<string>();
            Stack<string> directoriesToScan = new Stack<string>();

            directoriesToScan.Push(rootDirectory);

            while (directoriesToScan.Count > 0)
            {
                var dir = directoriesToScan.Pop();

                //scan directories
                if (visitSubdirectories)
                {
                    var subDirs = Directory.GetDirectories(dir);
                    for (int i = subDirs.Length - 1; i >= 0; i--)
                        directoriesToScan.Push(subDirs[i]);
                }

                //scan files
                selectedFiles.AddRange(Directory.GetFiles(dir).Where(
                    file =>
                    {
                        foreach (var extension in extensionsFilter)
                            if (file.EndsWith(extension))
                                return true;
                        return false;
                    }));
            }

            return selectedFiles;
        }

        #endregion
    }
}
